package com.icesoft.framework.core.base;


import com.icesoft.framework.core.exception.BusinessException;
import com.icesoft.framework.core.util.AnnotationUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.BiFunction;

@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@SuppressWarnings({"rawtypes", "unchecked"})
@Slf4j
public class WebRequestAnnotationInterceptor extends BaseHandlerInterceptor {

	private static final String GLOBE_REQUEST_START_TIME = "GLOBE_REQUEST_START_TIME";
	private static final Map<Method, Map<Class, Annotation>> methodInterceptorMap = new WeakHashMap<>(1000);
	@Autowired(required = false)
	List<BaseAnnotationInterceptor> interceptors;

	@Override
	public String pathPatten() {
		return "/**";
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		if (log.isTraceEnabled()) {
			request.setAttribute(GLOBE_REQUEST_START_TIME, System.currentTimeMillis());
			log.trace("url:{}", request.getRequestURI());
		}
		if (!(handler instanceof HandlerMethod) || interceptors == null) {
			return true;
		}
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		return forEach(handlerMethod, (interceptor, annotation) -> {
			try {
				if (!interceptor.preHandle(request, response, handlerMethod, annotation)) {
					if (log.isDebugEnabled()) {
						log.debug("请求{}被拦截，class={}", request.getRequestURI(), interceptor.getClass().getName());
					}
					return false;
				}
			} catch (BusinessException e) {
				throw e;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			return true;
		});
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
						   ModelAndView modelAndView) {
		if (log.isTraceEnabled()) {
			Long start = (Long) request.getAttribute(GLOBE_REQUEST_START_TIME);
			if (start == null) {
				log.warn("未找到开始时间 url:{}", request.getRequestURI());
			} else {
				log.trace("postHandle url:{},请求用时：{} ms", request.getRequestURI(),
						System.currentTimeMillis() - start);
			}
		}
		if (!(handler instanceof HandlerMethod) || interceptors == null) {
			return;
		}
		HandlerMethod handlerMethod = (HandlerMethod) handler;

		forEach(handlerMethod, (interceptor, annotation) -> {
			interceptor.postHandle(request, response, handlerMethod, modelAndView, annotation);
			return true;
		});
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
		if (log.isTraceEnabled()) {
			Long start = (Long) request.getAttribute(GLOBE_REQUEST_START_TIME);
			if (start == null) {
				log.warn("未找到开始时间 url:{}", request.getRequestURI());
			} else {
				log.trace("afterCompletion url:{},请求用时：{} ms", request.getRequestURI(),
						System.currentTimeMillis() - start);
			}
		}
		if (!(handler instanceof HandlerMethod) || interceptors == null) {
			return;
		}
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		forEach(handlerMethod, (interceptor, annotation) -> {
			interceptor.afterCompletion(request, response, handlerMethod, ex, annotation);
			return true;
		});
	}

	private boolean forEach(HandlerMethod handlerMethod,
							BiFunction<BaseAnnotationInterceptor, Annotation, Boolean> biFunction) {
		Map<Class, Annotation> map = methodInterceptorMap.get(handlerMethod.getMethod());
		if (map == null) {
			map = initMethodInterceptorMap(handlerMethod.getMethod());
		}
		for (BaseAnnotationInterceptor interceptor : interceptors) {
			Annotation annotation = map.get(interceptor.getClass());
			if (annotation != null) {
				if (!biFunction.apply(interceptor, annotation)) {
					return false;
				}
			}
		}
		return true;
	}

	private synchronized Map<Class, Annotation> initMethodInterceptorMap(Method method) {
		Map<Class, Annotation> map = methodInterceptorMap.get(method);
		if (map != null) {
			return map;
		}
		map = new HashMap<>();
		for (BaseAnnotationInterceptor interceptor : interceptors) {
			Annotation annotation = AnnotationUtil.findAnyAnnotation(method, interceptor.getAnnotationClass());
			if (annotation != null) {
				map.put(interceptor.getClass(), annotation);
			}
		}
		methodInterceptorMap.put(method, map);
		return map;
	}

}
